﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Trinity.Utilities;
using System.IO;
using System.Threading.Tasks;

namespace SequenceAssembler
{
    class SequenceRead
    {
        private string m_sequence;
        private long m_readId;
        private long[] m_kmers;

        public long ReadId
        {
            get { return m_readId; }
        }

        public long[] Kmers
        {
            get { return m_kmers; }
        }

        public SequenceRead(string sequence, long readId)
        {
            m_sequence = sequence;
            m_readId = readId;
        }

        public void CaculateKmers(short KmerLen)
        {
            //int len = readStr.Length - bg.KmerLen + 1;
            int len = m_sequence.Length - KmerLen + 1;
            m_kmers = new long[len];
            for (int i = 0; i < len; ++i)
            {
                m_kmers[i] = BaseOperation.kmerToLong(m_sequence, i, KmerLen);
            }
        }

        public void WriterKmers(BinaryWriter bw)
        {
            for (int i = 0; i < m_kmers.Length; ++i)
            {
                bw.Write(m_readId);
                bw.Write(m_kmers[i]);
            }
        }

    }
    class SequenceTransformer
    {
        private short m_kmerLen;
        private ReadFile[] m_sequenceReadFiles;
        private string m_transformFilePath = "";
        private string m_categoryFilePath = "";
        private BinaryWriter m_transformFileWriter;
        private BinaryWriter m_categoryFileWriter;
        private SequenceRead[] m_batchSequenceRead;
        private int m_batchNumber = 1024 * 1024 * 4;
        private int m_batchReadCounter = 0;
        private int m_totalReadCounter = 0;
        private int m_totoalKmerCounter = 0;
        private int m_binaryWriterBufferSize = 1024 * 1024 * 16;

        public SequenceTransformer(short kmerLen, ReadFile[] sequenceReadFiles, string transformFilePath, string categoryFilePath)
        {
            m_kmerLen = kmerLen;
            m_sequenceReadFiles = sequenceReadFiles;
            m_transformFilePath = transformFilePath;
            m_categoryFilePath = categoryFilePath;
            InitialWriter();
        }

        private void InitialWriter()
        {
            try
            {
                m_transformFileWriter = new BinaryWriter(new BufferedStream(new FileStream(m_transformFilePath, FileMode.Create, FileAccess.Write), m_binaryWriterBufferSize));
                m_categoryFileWriter = new BinaryWriter(new FileStream(m_categoryFilePath, FileMode.Create, FileAccess.Write));
                long count = 0;
                m_transformFileWriter.Write(count);
                m_categoryFileWriter.Write((long)m_sequenceReadFiles.Length);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
                Environment.Exit(0);
            }
        }

        public void Transform()
        {
            Reader reader = new Reader(0);
            foreach (ReadFile readFile in m_sequenceReadFiles)
            {
                Console.WriteLine("Processing '{0}' ......", readFile.Filename);
                reader.Open(readFile.Filename);
                feedReads(reader, readFile.InsertLength, readFile.InsertVariance);
                Console.WriteLine("Processing '{0}' Completed.", readFile.Filename);
            }
            AddCounterAndClose();
        }

        private int feedReads(Reader reader, int insLen = 100, double insVar = 25.0)
        {
            try
            {
                m_batchSequenceRead = new SequenceRead[m_batchNumber];
                AddCategory(reader.CurrentID + 1, insLen, insVar);
                while (reader.Next())
                {
                    //if (reader.CurrentID % 10000 == 0)
                    //{
                    //    Console.WriteLine("Processed {0} reads", reader.CurrentID);
                    //}
                    if (m_batchReadCounter != m_batchNumber)
                    {
                        SequenceRead sr = new SequenceRead(reader.CurrentRead, reader.CurrentID);
                        m_batchSequenceRead[m_batchReadCounter] = sr;
                        m_batchReadCounter += 1;
                        m_totalReadCounter += 1;
                    }
                    else
                    {
                        ProcessBatch();
                        m_batchSequenceRead = new SequenceRead[m_batchNumber];
                        m_batchReadCounter = 0;
                        SequenceRead sr = new SequenceRead(reader.CurrentRead, reader.CurrentID);
                        m_batchSequenceRead[m_batchReadCounter] = sr;
                        m_batchReadCounter += 1;
                        m_totalReadCounter += 1;
                        Console.WriteLine("Processed {0} reads", m_totalReadCounter);
                    }
                    
                }
                if (m_batchReadCounter != 0)
                {
                    ProcessBatch();
                    m_batchSequenceRead = null;
                }
            }
            catch (Exception e)
            {
                Log.WriteLine(e.Message);
                Log.WriteLine(e.StackTrace);
                Log.WriteLine("current read id = " + reader.CurrentID);
                Environment.Exit(0);
            }
            return 0;
        }

        private void ProcessBatch()
        {
            Parallel.For(0, m_batchReadCounter, i =>
            {
                m_batchSequenceRead[i].CaculateKmers(m_kmerLen);
            });
            for (int i = 0; i < m_batchReadCounter; ++i)
            {
                m_batchSequenceRead[i].WriterKmers(m_transformFileWriter);
                m_totoalKmerCounter += m_batchSequenceRead[i].Kmers.Length;
            }
        }

        private void AddCategory(long readId, int inslen, double insVar)
        {
            m_categoryFileWriter.Write(readId);
            m_categoryFileWriter.Write(inslen);
            m_categoryFileWriter.Write(insVar);
        }

        private void AddCounterAndClose()
        {
            m_transformFileWriter.Seek(0, SeekOrigin.Begin);
            m_transformFileWriter.Write(m_totoalKmerCounter);
            m_transformFileWriter.Close();
            m_categoryFileWriter.Close();
        }

        private void TestTransformFile()
        {
            BinaryReader br = null;
            try
            {
                br = new BinaryReader(new BufferedStream(new FileStream(m_transformFilePath, FileMode.Open, FileAccess.Read), m_binaryWriterBufferSize));
                long count = br.ReadInt64();
                for (long i = 0; i < count; ++i)
                {
                    br.ReadInt64();
                    br.ReadInt64();
                }
                try
                {
                    br.ReadByte();
                }
                catch (EndOfStreamException e)
                {
                    Console.WriteLine("absoulte ok");
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
            }
            finally
            {
                if (br != null)
                {
                    br.Close();
                }
            }
        }
    }
}
